﻿using ChartManager.Models;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace ChartManager.Controllers
{
    public static class ChartController
    {
        /// <summary>
        /// 增加榜单
        /// </summary>
        /// <param name="context">EFContext</param>
        /// <param name="year">年</param>
        /// <param name="month">月</param>
        /// <param name="day">日</param>
        /// <param name="RowsGiven">给定行</param>
        public static void AddChart(EFContext context, int year, int month, int day, params ChartRow[] RowsGiven)
        {
            // 与每周榜单数量不符
            if (RowsGiven.Count() != Constants.SongsCount)
                throw new ArgumentException("不符合预期数量", nameof(RowsGiven));
            var DateGiven = new DateTime(year, month, day);
            // 给出的年月日迟于当天
            if (DateGiven > DateTime.Now)
                throw new ArgumentOutOfRangeException(nameof(DateGiven), DateGiven, "非过去发生的榜单");
            // 给出的年月日非每周发榜日期
            if (DateGiven.DayOfWeek != Constants.ReleaseDay)
                throw new ArgumentException("非发榜日子", nameof(DateGiven));
            // 给出的年月日在数据库中已存在榜单
            if (context.Charts.Any(chart => DbFunctions.DiffDays(chart.ReleaseTime, DateGiven) == 0))
                throw new ArgumentException("与数据库重复", nameof(DateGiven));
            // 增加榜单
            var newChart = new Chart { ReleaseTime = DateGiven, Rows = RowsGiven.ToList() };
            context.Charts.Add(newChart);
            context.SaveChanges();
        }

        /// <summary>
        /// 增加榜单
        /// </summary>
        /// <param name="context">EFContext</param>
        /// <param name="year">年</param>
        /// <param name="month">月</param>
        /// <param name="day">日</param>
        /// <param name="SongsGiven">给定歌曲</param>
        public static void AddChart(EFContext context, int year, int month, int day, params Song[] SongsGiven)
        {
            // 与每周榜单数量不符
            if (SongsGiven.Count() != Constants.SongsCount)
                throw new ArgumentException("不符合预期数量", nameof(SongsGiven));
            // 给定歌曲中互相存在重复歌曲
            foreach (var song in SongsGiven)
            {
                // 只要obj与song不是同一个对象，并且歌曲歌手同名，则存在重复项
                if (SongsGiven.Any(obj => obj != song && song.Name.Equals(obj.Name) && obj.Singers.SingersEquals(song.Singers)))
                    throw new ArgumentException("存在重复项", nameof(SongsGiven));
            }
            var DateGiven = new DateTime(year, month, day);
            // 给出的年月日迟于当天
            if (DateGiven > DateTime.Now)
                throw new ArgumentOutOfRangeException(nameof(DateGiven), DateGiven, "非过去发生的榜单");
            // 给出的年月日非每周发榜日期
            if (DateGiven.DayOfWeek != Constants.ReleaseDay)
                throw new ArgumentException("非发榜日子", nameof(DateGiven));
            // 给出的年月日在数据库中已存在榜单
            if (context.Charts.Any(chart => DbFunctions.DiffDays(chart.ReleaseTime, DateGiven) == 0))
                throw new ArgumentException("与数据库重复", nameof(DateGiven));
            // 获取上周榜单
            var chartLastWeek = new Chart();
            try
            {
                chartLastWeek = GetLastWeek(context);
            }
            catch (IndexOutOfRangeException)
            {
                chartLastWeek = null;
            }
            var newChart = new Chart { ReleaseTime = DateGiven };
            var Rows = new ChartRow[Constants.SongsCount];
            for (int i = 0; i < Constants.SongsCount; i++)
            {
                // 若获取到上周榜单，则从上周榜单中找出与本次循环歌曲相同的行，若没有找到，返回null
                var queryLastRow = chartLastWeek != null ? (from qRow in chartLastWeek.Rows
                                                            where qRow.Song == SongsGiven[i]
                                                            select qRow) : null;
                // 找到相同的行但数量不为1或0，说明上周榜单出错，不应该继续进行
                // 否则，LastRow要么为null，此时不存在上周榜单，或者新进榜未出现在上周榜单中，要么为相同行，此时存在上周榜单
                ChartRow LastRow = null;
                try
                {
                    LastRow = queryLastRow?.SingleOrDefault();
                }
                catch (InvalidOperationException ex)
                {
                    throw new Exception("上周查询得到的数据错误", ex);
                }
                // 查询本次循环歌曲上一次出现在榜单中时的行
                var rowLastAppeared = (from qRow in context.Rows.ToList()
                                       where qRow.Song == SongsGiven[i] && qRow.Chart.ReleaseTime < DateGiven
                                       orderby qRow.Chart.ReleaseTime descending
                                       select qRow).FirstOrDefault();
                Rows[i] = new ChartRow();
                Rows[i].Chart = newChart;
                Rows[i].Rank = i + 1;
                Rows[i].Song = SongsGiven[i];
                // 若存在于上周榜单，则将上周的Rank赋值给本周的LastWeek，否则LastWeek为null
                Rows[i].LastWeek = LastRow?.Rank;
                // 若本次循环歌曲在榜单中出现过，则获取其WeekOn和Peak数据，WeekOn+1，Peak与Rank的较小值
                Rows[i].WeekOn = rowLastAppeared != null ? rowLastAppeared.WeekOn + 1 : 1;
                Rows[i].Peak = rowLastAppeared != null ? Math.Min(rowLastAppeared.Peak, Rows[i].Rank) : Rows[i].Rank;
            }
            // 循环结束，赋值到新榜单的行中
            newChart.Rows = Rows.ToList();
            context.Charts.Add(newChart);
            context.SaveChanges();
        }

        /// <summary>
        /// 获取上周数据
        /// </summary>
        /// <param name="context">EFContext</param>
        /// <returns>上周数据</returns>
        public static Chart GetLastWeek(EFContext context)
        {
            // 数据库中没有榜单，则没有上周数据
            if (!context.Charts.Any())
                throw new IndexOutOfRangeException("没有上周数据");
            // 按发布日期倒序取首个则为上周数据
            return context.Charts.OrderByDescending(chart => chart.ReleaseTime).First();
        }

        /// <summary>
        /// 获取所有榜单
        /// </summary>
        /// <param name="context">EFContext</param>
        /// <returns>榜单</returns>
        public static IEnumerable<Chart> GetCharts(EFContext context)
        {
            return context.Charts.AsEnumerable();
        }

        /// <summary>
        /// 获取单个榜单
        /// </summary>
        /// <param name="context">EFContext</param>
        /// <param name="time">发布日期</param>
        /// <returns>榜单</returns>
        public static Chart GetSingleChart(EFContext context, DateTime time)
        {
            // 此处不检查是否存在多个同日期的榜单
            return (from c in context.Charts
                    where DbFunctions.DiffDays(c.ReleaseTime, time.Date) == 0
                    select c).FirstOrDefault();
        }

        /// <summary>
        /// 获取该榜单的出榜歌曲
        /// </summary>
        /// <param name="context">EFContext</param>
        /// <param name="chart">需要查询的榜单</param>
        /// <returns>以行的形式返回</returns>
        public static IEnumerable<ChartRow> GetOutSongs(EFContext context, Chart chart)
        {
            // 不存在该榜单时返回null
            if (!context.Charts.ToList().Contains(chart)) return null;
            // 查询早于给定榜单的榜单中最迟发布的榜单，即为该榜单的上周榜单
            var lastweek = (from c in context.Charts
                            where DbFunctions.DiffDays(c.ReleaseTime, chart.ReleaseTime) > 0
                            orderby c.ReleaseTime descending
                            select c).FirstOrDefault();
            // 没有查询到上周榜单，可能为最早的榜单
            if (lastweek == null)
                throw new IndexOutOfRangeException("没有上周数据");
            var result = new List<ChartRow>();
            // 对上周各行进行遍历
            foreach (var row in lastweek.Rows)
            {
                // 本次循环行的歌曲中没有出现在本周榜单中时，加入到返回结果中
                if (!chart.Rows.Any(obj => obj.Song.Name.Equals(row.Song.Name) && obj.Song.Singers.SingersEquals(row.Song.Singers)))
                    result.Add(row);
            }
            // 按上周榜单的Rank倒序返回
            return result.OrderByDescending(row => row.Rank);
        }
    }
}
